package com.orm.parser;

import com.orm.annotation.*;
import com.orm.mapper.BaseMapper;
import com.orm.mapping.Configuration;
import com.orm.mapping.MapperStatement;
import com.orm.mapping.MappingProcess;
import com.orm.mapping.xmltag.*;
import com.orm.pool.Environment;
import com.orm.util.ClassUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.*;
import org.dom4j.io.SAXReader;
import org.dom4j.tree.DefaultElement;
import org.dom4j.tree.DefaultText;
import org.xml.sax.SAXException;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 负责解析xml 文件
 */
@Slf4j
@Data  //临时加下
public class XmlParser {
	private SAXReader reader = new SAXReader();
	
	private Document document;
	
	private InputStream xml;
	
	
	private List<String> paramName = new ArrayList<>(16);  // 保存所有 #{name}  test 条件
	
	// 加载xml文件
	public void load(InputStream xml) throws DocumentException {
		this.xml = xml;
		try {
			reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
			document = reader.read(xml);
		} catch (SAXException e) {
			e.printStackTrace();
		}
	}
	
	// 关闭文件
	public void close() throws IOException {
		xml.close();
	}
	
	
	// 解析连接参数
	public Environment parseEnv() {
		List list = document.selectNodes("//configuration/environments/environment/dataSource/property");
		Map<String, String> config = new HashMap<>(4);
		for (Object o : list) {
			Element e = (Element) o;
			config.put(e.attributeValue("name"), e.attributeValue("value"));
		}
		Environment env = new Environment();
		env.setUrl(config.get("url"));
		env.setDriver(config.get("driver"));
		env.setUsername(config.get("username"));
		env.setPassword(config.get("password"));
		return env;
	}
	
	// 获得所有mapper.xml路径
	public List<String> parseMapperPaths(Configuration config) throws DocumentException {
		ArrayList<String> mapperPaths = new ArrayList<>(16);
		// List mappers = document.selectNodes("/configuration/mappers/mapper");
		List mappers = document.selectNodes("/configuration/mappers/*");
		for (Object o : mappers) {
			Element e = (Element) o;
			
			if (e.getName().equals("mapper")) {
				String resource = e.attributeValue("resource");  // 暂时只支持 resource 属性, url 和class 不支持
				// 2022-01-10  增强处理:添加了对 * 号的支持，直接遍历所有文件，进行正则匹配查找
				//  不要写好几个*号影响匹配
				if (resource.contains("*")) {
					// 需要做匹配处理
					Pattern compile = Pattern.compile(resource.replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*?"));
					try {
						Enumeration<URL> resources = ClassUtil.getClassLoader().getResources("");
						while (resources.hasMoreElements()) {
							URI uri = resources.nextElement().toURI();
							String path = uri.getPath();
							if (path == null) {
								continue;
							}
							File file = new File(path);
							if (file.isDirectory()) {
								mapperPaths.addAll(find(file, compile));
							} else {
								//  ??  是文件的话无视掉
							}
						}
					} catch (URISyntaxException | IOException ex) {
						ex.printStackTrace();
					}
				} else {
					mapperPaths.add(resource);
				}
				
			} else if (e.getName().equals("package")) {
				
				// 是包的话，直接搜索一下
				String name = e.attributeValue("name").trim();
				Set<Class<?>> classes = ClassUtil.doScanClassName(name);
				config.getMapperClasses().addAll(classes);
			}
		}
		return mapperPaths;
	}
	
	
	// 解析所有mapper.xml
	public void parseMapper(Configuration config) {
		Set<Class<?>> mapperClasses = config.getMapperClasses();
		Map<String, MapperStatement> mapperStatements = config.getMapperStatements();
		
		Element root = document.getRootElement();
		String namespace = root.attributeValue("namespace").trim();
		Class<?> clazz = ClassUtil.loadClass(namespace);
		mapperClasses.add(clazz);
		
		List<Element> elements = root.elements();
		for (Element element : elements) {
			String tagName = element.getName();
			if (!(tagName.equals("select") || tagName.equals("insert") || tagName.equals("delete") || tagName.equals("update"))) {
				continue;
			}
			
			String id = element.attributeValue("id").trim();
			
			Method[] methods = clazz.getMethods();
			Method targetMethod = null;
			for (Method method : methods) {
				if (method.getName().equals(id)) {
					targetMethod = method;  // break;
					break;
				}
			}
			// xml 对应的方法不存在? 抛出异常?
			// 无视掉
			if (targetMethod == null) {
				log.info("xml文件中定义的 {}.{} 方法在接口中不存在", namespace, id);
				continue;
			}
			Parameter[] parameters = targetMethod.getParameters();
			
			
			String sqlType = element.getName();
			String sql = element.getTextTrim();
			String returnType = element.attributeValue("returnType");     // 只支持单层类型返回
			
			MapperStatement mapperStatement = new MapperStatement();
			mapperStatement.setId(id);
			mapperStatement.setSqlType(sqlType);
			mapperStatement.setSql(sql);
			// mapperStatement.setReturnType(returnType == null ? "" : returnType);  // 设为null ，留给后面解析返回类型,干脆xml 不写返回类型，少写点
			
			mapperStatements.put(namespace + "." + id, mapperStatement);
			
			
			// 动态节点
			MixedSqlNode rootNode = new MixedSqlNode();  // 生成根节点
			DefaultElement de = ((DefaultElement) element);
			for (int i = 0; i < de.nodeCount(); i++) {
				Node node = de.node(i);
				
				if (node instanceof DefaultText) {
					String textSql = node.getText().trim();
					
					if (textSql.isEmpty()) {
						continue;
					}
					
					// 也要检查
					MappingProcess.checkSql(parameters, textSql, namespace + "." + id);
					
					rootNode.addSqlNode(new StaticTextSqlNode(textSql));  // 修改
				} else if (node instanceof Element) {
					String name = node.getName();
					if (name.equals("where")) {
						WhereSqlNode whereSqlNode = new WhereSqlNode();
						rootNode.addSqlNode(whereSqlNode);
						List<Element> ifs = ((Element) node).elements();
						
						for (Element ifEle : ifs) {
							String test = ifEle.attributeValue("test").trim();
							String ifSql = ifEle.getTextTrim();
							
							// 2个都拿去检查
							MappingProcess.checkEL(parameters, test, namespace + "." + id);
							MappingProcess.checkSql(parameters, ifSql, namespace + "." + id);
							
							
							IfSqlNode ifSqlNode = new IfSqlNode(ifSql, test);
							whereSqlNode.addSqlNode(ifSqlNode);
						}
					} else if (name.equals("set")) {
						SetSqlNode setSqlNode = new SetSqlNode();
						rootNode.addSqlNode(setSqlNode);
						List<Element> ifs = ((Element) node).elements();
						
						for (Element ifEle : ifs) {
							String test = ifEle.attributeValue("test").trim();
							String ifSql = ifEle.getTextTrim();
							
							// 2个都拿去检查
							MappingProcess.checkEL(parameters, test, namespace + "." + id);
							MappingProcess.checkSql(parameters, ifSql, namespace + "." + id);
							
							
							IfSqlNode ifSqlNode = new IfSqlNode(ifSql, test);
							setSqlNode.addSqlNode(ifSqlNode);
						}
					}
				}
			}
			mapperStatement.setRoot(rootNode);   //  设置上根节点
		}
	}
	
	
	// 额外解析一次接口
	// 可能有的接口 没有把sql 写在 xml 中，而是在接口方法上
	public void parseInterface(Configuration config) {
		Set<Class<?>> mapperClasses = config.getMapperClasses();
		Map<String, MapperStatement> mapperStatements = config.getMapperStatements();
		
		for (Class<?> mapperClass : mapperClasses) {
			
			if (BaseMapper.class.isAssignableFrom(mapperClass)) {
				// 如果接口类是 继承了 BaseMapper ,则需要额外注册 BaseMapper 里面的几个函数
				registryBaseMapperStatement(mapperClass, config);
			}
			
			
			// 遍历接口类所有方法
			Method[] methods = mapperClass.getMethods();
			
			for (Method method : methods) {
				String id = mapperClass.getName() + "." + method.getName();
				MapperStatement mapperStatement = mapperStatements.get(id);   // 根据 类名.方法名 查找下 mapperStatement
				
				/**
				 * sql 写在xml 中
				 * sql 写在接口注解上
				 */
				if (mapperStatement == null && isSqlAnnotationPresent(method)) {
					// 已解析的结果中没有这个方法 ,且该方法存在注解,则补充下sql 语句
					
					mapperStatement = new MapperStatement();
					mapperStatement.setId(method.getName());
					// mapperStatement.setReturnType(method.getReturnType().getTypeName());  // 等下在下面填充
					
					Select select = method.getAnnotation(Select.class);
					Update update = method.getAnnotation(Update.class);
					Insert insert = method.getAnnotation(Insert.class);
					Delete delete = method.getAnnotation(Delete.class);
					if (select != null) {
						mapperStatement.setSqlType("select");
						String sql = select.value();
						
						// 需要分析出所有的  #{name} 格式语句
						MappingProcess.checkSql(method.getParameters(), sql, method.getDeclaringClass().getName() + "." + method.getName());
						
						mapperStatement.setSql(sql);
						
					} else if (update != null) {
						mapperStatement.setSqlType("update");
						mapperStatement.setSql(update.value());
					} else if (insert != null) {
						mapperStatement.setSqlType("insert");
						mapperStatement.setSql(insert.value());
					} else if (delete != null) {
						mapperStatement.setSqlType("delete");
						mapperStatement.setSql(delete.value());
					}
					mapperStatements.put(id, mapperStatement);
				}
				
				
				if (mapperStatement != null) {
					// 补充返回类型
					if (mapperStatement.getReturnType() == null) {
						
						Class<?> returnType = method.getReturnType();
						
						if (returnType.equals(void.class)) {
							mapperStatement.setReturnVoid(true);
						} else if (List.class.isAssignableFrom(returnType)) {         // 不支持数组
							mapperStatement.setReturnMany(true);
							// returnType.isArray()  判断是否是数组
						} else if (Number.class.isAssignableFrom(returnType)) {     // 包装类型数字一律
							mapperStatement.setReturnCount(true);
						} else if (returnType.equals(int.class) || returnType.equals(long.class)) {
							mapperStatement.setReturnCount(true);
						} else {
							mapperStatement.setReturnOne(true);     // 其他情况一律作为 实体类 ，非实体类错误一概不管
						}
						
						// 拿到返回类型
						Type genericReturnType = method.getGenericReturnType();
						if (genericReturnType instanceof ParameterizedType) {
							Type type = ((ParameterizedType) genericReturnType).getActualTypeArguments()[0];
							mapperStatement.setReturnType(((Class<?>) type));
						} else {
							// 不是泛型参数一律视为普通类型
							mapperStatement.setReturnType((Class<?>) genericReturnType);
						}
					}
				}
				
				
				// 解析下 参数名映射关系，必须固定参数数量
				if (mapperStatement != null && mapperStatement.getParamIndexToName() == null) {
					mapperStatement.setParamIndexToName(resolverParamName(method));
				}
			}
		}
	}
	
	
	public static boolean isSqlAnnotationPresent(Method method) {
		return method.isAnnotationPresent(Select.class) ||
				method.isAnnotationPresent(Update.class) ||
				method.isAnnotationPresent(Insert.class) ||
				method.isAnnotationPresent(Delete.class);
	}
	
	
	private static List<String> find(File root, Pattern destPattern) throws IOException {
		ArrayList<String> files = new ArrayList<>();
		if (root.isFile()) {
			Matcher matcher = destPattern.matcher(root.getPath().replaceAll("\\\\", "/"));
			if (matcher.find()) {
				files.add(matcher.group(0));
			}
		} else {
			File[] file = root.listFiles();
			for (File t : file) {
				files.addAll(find(t, destPattern));
			}
		}
		return files;
	}
	
	
	// 解析方法参数名和顺序之间的映射，并保存到 MapperStatement 上
	public static SortedMap<Integer, String> resolverParamName(Method method) {
		TreeMap<Integer, String> names = new TreeMap<>();
		
		Parameter[] parameters = method.getParameters();
		int index = 0;
		for (Parameter parameter : parameters) {
			String name = parameter.getName();
			
			if (parameter.isAnnotationPresent(Param.class)) {
				Param param = parameter.getAnnotation(Param.class);
				String newName = param.value();
				if (!newName.isEmpty()) {
					
					if (names.containsValue(newName)) {
						log.error("@Param(value=\"{}\")  和已有参数名重复", param.value());
						throw new RuntimeException("@Param参数名和已有参数名重复");
					}
					name = newName;
				}
			}
			
			names.put(index++, name);
		}
		return names;
	}
	
	
	/**
	 * 注册 所有继承了  BaseMapper<T> 接口
	 */
	private void registryBaseMapperStatement(Class<?> mapperClass, Configuration config) {
		Type[] genericInterfaces = mapperClass.getGenericInterfaces();
		
		Type type = null;
		
		for (Type genericInterface : genericInterfaces) {
			if (genericInterface instanceof ParameterizedType) {
				type = ((ParameterizedType) genericInterface).getActualTypeArguments()[0];
				// type 就是 BaseMapper<T>上面的T
				break;
			}
		}
		Class<?> clazz = (Class) type;
		
		String tableName = clazz.getSimpleName().replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
		TableName tn = clazz.getAnnotation(TableName.class);
		if (tn != null && !tn.value().isEmpty()) {
			tableName = tn.value();
		}
		
		String baseMapper = BaseMapper.class.getName();
		
		// 注入sql mapperStatement
		config.getMapperStatements().put(mapperClass.getName() + ".insert", handlerInsert(clazz, tableName));
		config.getMapperStatements().put(mapperClass.getName() + ".selectById", handlerSelectById(clazz, tableName));
		config.getMapperStatements().put(mapperClass.getName() + ".updateById", handlerUpdateById(clazz, tableName));
		config.getMapperStatements().put(mapperClass.getName() + ".deleteById", handlerDeleteById(clazz, tableName));
		config.getMapperStatements().put(mapperClass.getName() + ".selectAll", handlerSelectAll(clazz, tableName));
		config.getMapperStatements().put(mapperClass.getName() + ".count", handlerCount(clazz, tableName));
	}
	
	
	// 处理 int insert(T entity);
	public MapperStatement handlerInsert(Class<?> clazz, String tableName) {
		StringBuilder builder = new StringBuilder("insert into " + tableName);
		List<String> field1 = new ArrayList<>(16);  // 保存写入数据库的字段列表
		List<String> field2 = new ArrayList<>(16);  // 保存映射到实体类的属性名
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			if (field.isAnnotationPresent(NotField.class)) {
				continue;
			}
			String propertyName = field.getName();
			String columnName = propertyName.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
			
			TableFiled tableFiled = field.getAnnotation(TableFiled.class);
			if (tableFiled != null && tableFiled.exist() && !tableFiled.value().isEmpty()) {
				columnName = tableFiled.value();
			}
			
			/**
			 * 2022-01-19  bug 修复，直接使用 columnName 可能会让sql 出错，因为 属性名可能是 sql 的关键字，需要用 `` 引用起来
			 *
			 */
			// field1.add(columnName);       // bug 不能直接用列名去查
			field1.add("`"+columnName+"`");
			
			field2.add("#{entity." + propertyName + "}");   // 加不加前缀呢
		}
		builder.append("(");
		builder.append(String.join(",", field1));
		builder.append(") value(");
		builder.append(String.join(",", field2));
		builder.append(")");
		
		MapperStatement mapperStatement = new MapperStatement();
		mapperStatement.setId("insert");
		mapperStatement.setSqlType("insert");
		mapperStatement.setSql(builder.toString());
		mapperStatement.setReturnType(int.class);
		mapperStatement.setReturnCount(true);
		
		SortedMap<Integer, String> paramIndexToName = new TreeMap<>();
		paramIndexToName.put(0, "entity");
		mapperStatement.setParamIndexToName(paramIndexToName);
		
		return mapperStatement;
	}
	
	
	// 处理 T selectById(Serializable id);
	public MapperStatement handlerSelectById(Class<?> clazz, String tableName) {
		String sql = "select * from " + tableName + " where id = #{id}";
		
		MapperStatement mapperStatement = new MapperStatement();
		mapperStatement.setId("selectById");
		mapperStatement.setSqlType("select");
		mapperStatement.setSql(sql);
		mapperStatement.setReturnType(clazz);
		mapperStatement.setReturnOne(true);
		
		SortedMap<Integer, String> paramIndexToName = new TreeMap<>();
		paramIndexToName.put(0, "id");
		mapperStatement.setParamIndexToName(paramIndexToName);
		
		return mapperStatement;
	}
	
	// 处理 int updateById(T entity)       // update 表名 set 列名 = #{字段}, 列名 = #{字段} where id = #{id}
	public MapperStatement handlerUpdateById(Class<?> clazz, String tableName) {
		StringBuilder builder = new StringBuilder("insert into " + tableName);
		
		MixedSqlNode root = new MixedSqlNode();
		root.addSqlNode(new StaticTextSqlNode("update " + tableName + " "));
		
		SetSqlNode setSqlNode = new SetSqlNode();
		root.addSqlNode(setSqlNode);
		
		Field[] fields = clazz.getDeclaredFields();
		
		// 先搜索一下主键字段
		String keyName = null;
		Field keyField = null;
		for (Field field : fields) {
			if (field.isAnnotationPresent(TableId.class)) {
				TableId annotation = field.getAnnotation(TableId.class);
				keyName = annotation.value();
				if (keyName.isEmpty()) {
					keyName = field.getName().replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
				}
				keyField = field;
				break;
			}
		}
		
		if (keyField == null) {
			// 没有查到 @TableId 字段
			for (Field field : fields) {
				if (field.getName().equals("id")) {
					keyName = "id";
					keyField = field;
					break;
				}
			}
		}
		
		if (keyField == null) {
			throw new RuntimeException("实体类中不知道哪个是主键字段，无法构建sql语句");
		}
		
		for (Field field : fields) {
			if (field.isAnnotationPresent(NotField.class)) {
				continue;
			}
			if (field == keyField) {
				continue;
			}
			
			String propertyName = field.getName();
			String columnName = propertyName.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
			
			TableFiled tableFiled = field.getAnnotation(TableFiled.class);
			if (tableFiled != null && tableFiled.exist() && !tableFiled.value().isEmpty()) {
				columnName = tableFiled.value();
			}
			
			/**
			 * 2022-01-19  bug 修复，直接使用 columnName 可能会让sql 出错，因为 属性名可能是 sql 的关键字，需要用 `` 引用起来
			 */
			//  内容  columnName = #{entity.propertyName}     test条件 entity.propertyName
			IfSqlNode ifSqlNode = new IfSqlNode(" `" + columnName + "` = " + "#{entity." + propertyName + "} ", "entity." + propertyName);
			
			
			setSqlNode.addSqlNode(ifSqlNode);
		}
		root.addSqlNode(new StaticTextSqlNode(" where id = #{entity." + keyName + "}"));
		
		
		MapperStatement mapperStatement = new MapperStatement();
		mapperStatement.setId("updateById");
		mapperStatement.setSqlType("update");
		mapperStatement.setRoot(root);
		mapperStatement.setReturnType(int.class);
		mapperStatement.setReturnCount(true);
		
		SortedMap<Integer, String> paramIndexToName = new TreeMap<>();
		paramIndexToName.put(0, "entity");
		mapperStatement.setParamIndexToName(paramIndexToName);
		
		return mapperStatement;
	}
	
	
	// 处理 int deleteById(Serializable id);  // delete from 表名 where id = #{id}
	public MapperStatement handlerDeleteById(Class<?> clazz, String tableName) {
		String sql = "delete  from " + tableName + " where id = #{id}";
		
		MapperStatement mapperStatement = new MapperStatement();
		mapperStatement.setId("deleteById");
		mapperStatement.setSqlType("delete");
		mapperStatement.setSql(sql);
		mapperStatement.setReturnType(int.class);
		mapperStatement.setReturnCount(true);
		
		SortedMap<Integer, String> paramIndexToName = new TreeMap<>();
		paramIndexToName.put(0, "id");
		mapperStatement.setParamIndexToName(paramIndexToName);
		
		return mapperStatement;
	}
	
	
	// 处理 List<T> selectAll();  // select * from 表名
	public MapperStatement handlerSelectAll(Class<?> clazz, String tableName) {
		String sql = "select  *   from " + tableName;
		
		MapperStatement mapperStatement = new MapperStatement();
		mapperStatement.setId("selectAll");
		mapperStatement.setSqlType("select");
		mapperStatement.setSql(sql);
		mapperStatement.setReturnType(clazz);
		mapperStatement.setReturnMany(true);
		
		SortedMap<Integer, String> paramIndexToName = new TreeMap<>();  // 没有参数
		mapperStatement.setParamIndexToName(paramIndexToName);
		
		return mapperStatement;
	}
	
	
	// 处理 int count();;  // select count(1) from 表名
	public MapperStatement handlerCount(Class<?> clazz, String tableName) {
		String sql = "select  count(1)   from " + tableName;
		
		MapperStatement mapperStatement = new MapperStatement();
		mapperStatement.setId("count");
		mapperStatement.setSqlType("select");
		mapperStatement.setSql(sql);
		mapperStatement.setReturnType(int.class);
		mapperStatement.setReturnCount(true);
		
		SortedMap<Integer, String> paramIndexToName = new TreeMap<>();  // 没有参数
		mapperStatement.setParamIndexToName(paramIndexToName);
		
		return mapperStatement;
	}
}
